package com.beiming.libra.config.swagger;

import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Lists;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.service.Parameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;

import java.util.*;
import java.util.stream.Collectors;


@Configuration
@Import({
    Swagger2Configuration.class
})
public class SwaggerAutoConfiguration implements BeanFactoryAware {

  private BeanFactory beanFactory;

  @Bean
  @ConditionalOnMissingBean
  public SwaggerProperties swaggerProperties() {
    return new SwaggerProperties();
  }

  @Bean
  @ConditionalOnMissingBean
  @ConditionalOnProperty(name = "swagger.enabled", matchIfMissing = true)
  public List<Docket> createRestApi(SwaggerProperties swaggerProperties) {
    ConfigurableBeanFactory configurableBeanFactory = (ConfigurableBeanFactory) beanFactory;
    List<Docket> docketList = new LinkedList<>();
    // 没有分组
    if (swaggerProperties.getDocket().size() == 0) {
      ApiInfo apiInfo = new ApiInfoBuilder()
          .title(swaggerProperties.getTitle())
          .description(swaggerProperties.getDescription())
          .version(swaggerProperties.getVersion())
          .license(swaggerProperties.getLicense())
          .licenseUrl(swaggerProperties.getLicenseUrl())
          .contact(new Contact(swaggerProperties.getContact().getName(),
              swaggerProperties.getContact().getUrl(),
              swaggerProperties.getContact().getEmail()))
          .termsOfServiceUrl(swaggerProperties.getTermsOfServiceUrl())
          .build();
      if (swaggerProperties.getBasePath().isEmpty()) {
        swaggerProperties.getBasePath().add("/**");
      }
      List<Predicate<String>> basePath = new ArrayList();
      for (String path : swaggerProperties.getBasePath()) {
        basePath.add(PathSelectors.ant(path));
      }
      List<Predicate<String>> excludePath = new ArrayList();
      for (String path : swaggerProperties.getExcludePath()) {
        excludePath.add(PathSelectors.ant(path));
      }
      Docket docket = new Docket(DocumentationType.SWAGGER_2)
          .host(swaggerProperties.getHost())
          .apiInfo(apiInfo)
          .globalOperationParameters(buildGlobalOperationParametersFromSwaggerProperties(
              swaggerProperties.getGlobalOperationParameters()))
          .select()
          .apis(RequestHandlerSelectors.basePackage(swaggerProperties.getBasePackage()))
          .paths(
              Predicates.and(
                  Predicates.not(Predicates.or(excludePath)),
                  Predicates.or(basePath)
              )
          )
          .build();
      configurableBeanFactory.registerSingleton("defaultDocket", docket);
      docketList.add(docket);
      return docketList;
    }
    for (String groupName : swaggerProperties.getDocket().keySet()) {
      SwaggerProperties.DocketInfo docketInfo = swaggerProperties.getDocket().get(groupName);
      ApiInfo apiInfo = new ApiInfoBuilder()
          .title(docketInfo.getTitle().isEmpty() ? swaggerProperties.getTitle()
              : docketInfo.getTitle())
          .description(docketInfo.getDescription().isEmpty() ? swaggerProperties.getDescription()
              : docketInfo.getDescription())
          .version(docketInfo.getVersion().isEmpty() ? swaggerProperties.getVersion()
              : docketInfo.getVersion())
          .license(docketInfo.getLicense().isEmpty() ? swaggerProperties.getLicense()
              : docketInfo.getLicense())
          .licenseUrl(docketInfo.getLicenseUrl().isEmpty() ? swaggerProperties.getLicenseUrl()
              : docketInfo.getLicenseUrl())
          .contact(
              new Contact(
                  docketInfo.getContact().getName().isEmpty() ? swaggerProperties.getContact()
                      .getName() : docketInfo.getContact().getName(),
                  docketInfo.getContact().getUrl().isEmpty() ? swaggerProperties.getContact()
                      .getUrl() : docketInfo.getContact().getUrl(),
                  docketInfo.getContact().getEmail().isEmpty() ? swaggerProperties.getContact()
                      .getEmail() : docketInfo.getContact().getEmail()
              )
          )
          .termsOfServiceUrl(docketInfo.getTermsOfServiceUrl().isEmpty() ? swaggerProperties.getTermsOfServiceUrl()
                  : docketInfo.getTermsOfServiceUrl())
          .build();
      if (docketInfo.getBasePath().isEmpty()) {
        docketInfo.getBasePath().add("/**");
      }
      List<Predicate<String>> basePath = new ArrayList();
      for (String path : docketInfo.getBasePath()) {
        basePath.add(PathSelectors.ant(path));
      }
      List<Predicate<String>> excludePath = new ArrayList();
      for (String path : docketInfo.getExcludePath()) {
        excludePath.add(PathSelectors.ant(path));
      }
      Docket docket = new Docket(DocumentationType.SWAGGER_2)
          .host(swaggerProperties.getHost())
          .apiInfo(apiInfo)
          .globalOperationParameters(
              assemblyGlobalOperationParameters(swaggerProperties.getGlobalOperationParameters(),
                  docketInfo.getGlobalOperationParameters()))
          .groupName(groupName).select()
          .apis(RequestHandlerSelectors.basePackage(docketInfo.getBasePackage()))
          .paths(
              Predicates.and(
                  Predicates.not(Predicates.or(excludePath)),
                  Predicates.or(basePath)
              )
          ).build();
      configurableBeanFactory.registerSingleton(groupName, docket);
      docketList.add(docket);
    }
    return docketList;
  }

  @Override
  public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
    this.beanFactory = beanFactory;
  }

  private List<Parameter> buildGlobalOperationParametersFromSwaggerProperties(
      List<SwaggerProperties.GlobalOperationParameter> globalOperationParameters) {
    List<Parameter> parameters = Lists.newArrayList();

    if (Objects.isNull(globalOperationParameters)) {
      return parameters;
    }
    for (SwaggerProperties.GlobalOperationParameter globalOperationParameter : globalOperationParameters) {
      parameters.add(new ParameterBuilder()
          .name(globalOperationParameter.getName())
          .description(globalOperationParameter.getDescription())
          .modelRef(new ModelRef(globalOperationParameter.getModelRef()))
          .parameterType(globalOperationParameter.getParameterType())
          .required(Boolean.parseBoolean(globalOperationParameter.getRequired()))
          .build());
    }
    return parameters;
  }

  /**
   * 局部参数按照name覆盖局部参数
   */
  private List<Parameter> assemblyGlobalOperationParameters(
      List<SwaggerProperties.GlobalOperationParameter> globalOperationParameters,
      List<SwaggerProperties.GlobalOperationParameter> docketOperationParameters) {

    if (Objects.isNull(docketOperationParameters) || docketOperationParameters.isEmpty()) {
      return buildGlobalOperationParametersFromSwaggerProperties(globalOperationParameters);
    }

    Set<String> docketNames = docketOperationParameters.stream()
        .map(SwaggerProperties.GlobalOperationParameter::getName)
        .collect(Collectors.toSet());

    List<SwaggerProperties.GlobalOperationParameter> resultOperationParameters = Lists
        .newArrayList();

    if (Objects.nonNull(globalOperationParameters)) {
      for (SwaggerProperties.GlobalOperationParameter parameter : globalOperationParameters) {
        if (!docketNames.contains(parameter.getName())) {
          resultOperationParameters.add(parameter);
        }
      }
    }

    resultOperationParameters.addAll(docketOperationParameters);
    return buildGlobalOperationParametersFromSwaggerProperties(resultOperationParameters);
  }
}
